package com.gitee.ly.ymorm.executor.handler;

import com.gitee.ly.ymorm.YmOrmConfig;
import com.gitee.ly.ymorm.exceptions.YmOrmException;
import com.gitee.ly.ymorm.executor.Executor;
import com.gitee.ly.ymorm.mapping.BoundSql;
import com.gitee.ly.ymorm.mapping.ParamMap;
import com.gitee.ly.ymorm.mapping.YmOrmStatement;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * BaseStatementHandler
 *
 * @author ly
 **/
public abstract class BaseStatementHandler implements StatementHandler {

    protected final YmOrmConfig configuration;
    protected final Executor executor;
    protected final YmOrmStatement ymOrmStatement;
    protected BoundSql boundSql;

    protected BaseStatementHandler(Executor executor, YmOrmStatement ymOrmStatement, ParamMap parameterObject, BoundSql boundSql) {
        this.configuration = ymOrmStatement.getConfiguration();
        this.executor = executor;
        this.ymOrmStatement = ymOrmStatement;
        if (boundSql == null) {
            boundSql = ymOrmStatement.getBoundSql(parameterObject);
        }
        this.boundSql = boundSql;
    }

    @Override
    public BoundSql getBoundSql() {
        return boundSql;
    }

    @Override
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        Statement statement = null;
        try {
            statement = instantiateStatement(connection);
            setStatementTimeout(statement, transactionTimeout);
            setFetchSize(statement);
            return statement;
        } catch (SQLException e) {
            closeStatement(statement);
            throw e;
        } catch (Exception e) {
            closeStatement(statement);
            throw new YmOrmException("Error preparing statement.  Cause: " + e, e);
        }
    }

    protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

    @Override
    public void parameterize(Statement statement) throws SQLException {
        if (statement instanceof PreparedStatement) {
            PreparedStatement preparedStatement = (PreparedStatement) statement;
            List<String> parameterMappings = boundSql.getParameterMappings();
            if (parameterMappings != null) {
                ParamMap parameterObject = boundSql.getParameterObject();
                int index = 1;
                for (String paramName : parameterMappings) {
                    preparedStatement.setObject(index++, parameterObject.get(paramName));
                }
            }
        }
    }

    protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
        Integer queryTimeout = null;
        if (ymOrmStatement.getTimeout() != null) {
            queryTimeout = ymOrmStatement.getTimeout();
        }
        if (queryTimeout != null) {
            stmt.setQueryTimeout(queryTimeout);
        }
        applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
    }

    protected void setFetchSize(Statement stmt) throws SQLException {
        Integer fetchSize = ymOrmStatement.getFetchSize();
        if (fetchSize != null) {
            stmt.setFetchSize(fetchSize);
            return;
        }
    }

    protected void closeStatement(Statement statement) {
        try {
            if (statement != null) {
                statement.close();
            }
        } catch (SQLException e) {
            // ignore
        }
    }

    public static void applyTransactionTimeout(Statement statement, Integer queryTimeout, Integer transactionTimeout) throws SQLException {
        if (transactionTimeout == null) {
            return;
        }
        if (queryTimeout == null || queryTimeout == 0 || transactionTimeout < queryTimeout) {
            statement.setQueryTimeout(transactionTimeout);
        }
    }

    protected <E> List<E> resultHandler(Statement statement, Class<? extends E> resultType) throws SQLException {
        List list = new ArrayList();
        ResultSet resultSet = statement.getResultSet();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (resultSet.next()) {
                Object value = rowTypeDefaultValue(resultType);
                for (int index = 1; index <= columnCount; index++) {
                    String columnName = metaData.getColumnName(index);
                    int columnJdbcType = metaData.getColumnType(index);
                    Object javaValue = getColumnJavaTypeValue(resultSet.getObject(index), columnJdbcType);
                    if (resultType == String.class) {
                        value = javaValue == null ? null : javaValue.toString();
                        //String每一行只取第一个字段
                        break;
                    } else if (resultType == null || resultType == Map.class) {
                        Map<String, Object> valueMap = (Map<String, Object>) value;
                        valueMap.put(columnName, javaValue);
                    }
                }
                list.add(value);
            }
        } catch (SQLException e) {
            throw e;
        } finally {
            if (resultSet != null && !resultSet.isClosed()) {
                resultSet.close();
            }
        }
        return list;
    }

    private Object getColumnJavaTypeValue(Object object, int jdbcType) {
        if (object == null) {
            return null;
        }
        return object;
    }

    private Object rowTypeDefaultValue(Class resultType) {
        Object value = null;
        if (resultType == String.class) {
            value = null;
        } else if (resultType == null || resultType == Map.class) {
            value = new HashMap<String, Object>();
        }
        return value;
    }
}
